package com.study.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.study.cacheManager.UserInfoCacheManager;
import com.study.common.UserInfoDTO;
import com.study.constant.RedisKeyConstant;
import com.study.constant.RedissonLockConstant;
import com.study.convention.errorCode.BaseErrorCode;
import com.study.convention.exception.ClientException;
import com.study.convention.exception.ServiceException;
import com.study.dao.entity.TUser;
import com.study.dao.mapper.TUserMapper;
import com.study.dto.req.UserLoginReqDTO;
import com.study.dto.req.UserRegisterReqDTO;
import com.study.dto.req.UserUpdateReqDTO;
import com.study.dto.resp.UserLoginRespDTO;
import com.study.dto.resp.UserRespDTO;
import com.study.service.UserService;
import com.study.utils.JwtUtils;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.ObjectUtils;

import java.nio.charset.StandardCharsets;

import static com.study.common.UserContext.getUsername;

/**
 * @author canace
 * @version 1.0
 * @description 用户服务实现类
 * @date 2024/1/23 12:18
 */

@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements UserService {

    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    private final RedissonClient redissonClient;

    private final StringRedisTemplate stringRedisTemplate;

    private final UserInfoCacheManager userInfoCacheManager;

    //null,表示无限制，true表示若父类中属性值为空则忽略，不传给子类
    private final CopyOptions copyOption = CopyOptions.create(null, true);

    @Override
    public Boolean hasUsername(String username) {
        // 如果用户名不存在于布隆过滤器中，返回 true；如果用户名可能存在于布隆过滤器中，返回 false
        return !userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    public void register(UserRegisterReqDTO requestParam) {
        // 先检查用户是否已经注册
        if (!hasUsername(requestParam.getUsername())) {
            throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
        }

        // 分布式锁
        RLock lock = redissonClient.getLock(RedissonLockConstant.LOCK_USER_REGISTER_KEY + requestParam.getUsername());

        // 尝试获取锁
        try {
            if (lock.tryLock()) {
                try {
                    TUser tUser = new TUser();
                    BeanUtil.copyProperties(requestParam, tUser);
                    // 对密码进行加密
                    tUser.setPassword(DigestUtils.md5DigestAsHex(requestParam.getPassword().getBytes(StandardCharsets.UTF_8)));
                    // 将用户名放入数据库
                    int insert = baseMapper.insert(tUser);
                    if (insert < 1) {
                        throw new ClientException(BaseErrorCode.USER_REGISTER_ERROR);
                    }
                } catch (DuplicateKeyException e) {
                    // 如果用户名已经存在，抛出异常(DuplicateKeyException表示主键异常，即该用户名已经存在)
                    throw new ClientException(BaseErrorCode.USER_NAME_EXIST_ERROR);
                }
                // 将用户名添加到布隆过滤器中
                userRegisterCachePenetrationBloomFilter.add(requestParam.getUsername());
            }
        } finally {
            // 释放锁
            lock.unlock();
        }

    }

    @Override
    public UserLoginRespDTO login(UserLoginReqDTO userLoginReqDTO) {

        // 先检查用户是否存在
        if (hasUsername(userLoginReqDTO.getUsername())) {
            throw new ClientException(BaseErrorCode.USER_NAME_NOT_EXIST_ERROR);
        }

        // 根据用户名查询用户信息
//        LambdaQueryWrapper<TUser> eq = Wrappers.lambdaQuery(TUser.class)
//                .eq(TUser::getUsername, userLoginReqDTO.getUsername());
//        TUser tUser = baseMapper.selectOne(eq);
        TUser tUser = userInfoCacheManager.getUserInfoByUsername(userLoginReqDTO.getUsername());
        // 表示用户名不存在
        if (ObjectUtils.isEmpty(tUser)) {
            throw new ClientException(BaseErrorCode.USER_NAME_NOT_EXIST_ERROR);
        }

        // 判读用户账号是否被封禁
        if (tUser.getDelFlag() != 0) {
            throw new ClientException(BaseErrorCode.USER_NAME_IS_BE_BANNED_ERROR);
        }

        // 判断用户密码是否正确
        if (!DigestUtils.md5DigestAsHex(userLoginReqDTO.getPassword().getBytes(StandardCharsets.UTF_8)).equals(tUser.getPassword())) {
            throw new ClientException(BaseErrorCode.PASSWORD_VERIFY_ERROR);
        }

        // 生成token
        UserInfoDTO userInfoDTO = UserInfoDTO.builder().userId(tUser.getId().toString()).username(tUser.getUsername()).realName(tUser.getRealName()).build();
        String token = JwtUtils.getToken(userInfoDTO);

        // 放入redis，缓存时间1天
        stringRedisTemplate.opsForHash().put(RedisKeyConstant.USER_LOGIN_KEY + tUser.getUsername(), tUser.getId().toString(), token);
        stringRedisTemplate.expire(RedisKeyConstant.USER_LOGIN_KEY + tUser.getUsername(), 1, java.util.concurrent.TimeUnit.DAYS);

        return UserLoginRespDTO.builder().token(token).build();
    }

    @Override
    public void logout() {
        String username = getUsername();
        stringRedisTemplate.delete(RedisKeyConstant.USER_LOGIN_KEY + username);
    }

    @Override
    public UserRespDTO getUserByUsername(String username) {
        // 先检查用户是否存在
        if (hasUsername(username)) {
            throw new ClientException(BaseErrorCode.USER_NAME_NOT_EXIST_ERROR);
        }

        TUser tUser = userInfoCacheManager.getUserInfoByUsername(username);
        if (ObjectUtils.isEmpty(tUser)) {
            throw new ServiceException(BaseErrorCode.USER_IS_NOT_EXIST_ERROR);
        }
        UserRespDTO result = new UserRespDTO();
        BeanUtil.copyProperties(tUser, result);
        return result;
    }

    @Override
    public void updateUserInfo(UserUpdateReqDTO requestParam) {
        String username = requestParam.getUsername();
        // 先检查用户是否存在 设置用户名不能修改
        // 由于这里接口设计的问题，导致无法通过线程池获取当前的用户名，所有这里就以传入的用户名为准
        if (hasUsername(username)) {
            throw new ClientException(BaseErrorCode.USER_NAME_NOT_EXIST_ERROR);
        }

        TUser tUser = userInfoCacheManager.getUserInfoByUsername(username);
        if (ObjectUtils.isEmpty(tUser)) {
            throw new ServiceException(BaseErrorCode.USER_IS_NOT_EXIST_ERROR);
        }

        TUser updateUser = new TUser();

        // 先将原来的数据拷贝过来 (空值不拷贝)
        BeanUtil.copyProperties(tUser, updateUser, copyOption);
        // 再将修改的数据拷贝过来(空值不拷贝)
        BeanUtil.copyProperties(requestParam, updateUser, copyOption);
        // 判断密码是否为空，为空表示密码不用修改
        if (!ObjectUtils.isEmpty(requestParam.getPassword())) {
            updateUser.setPassword(DigestUtils.md5DigestAsHex(requestParam.getPassword().getBytes(StandardCharsets.UTF_8)));
        } else {
            updateUser.setPassword(tUser.getPassword());
        }

        // 获取分布式锁进行更新操作 这里使用用户名作为分布式锁的key，因为一个用户只能对这个表做一个更新/删除操作
        RLock lock = redissonClient.getLock(RedissonLockConstant.LOCK_USER_UPDATE_KEY + requestParam.getUsername());

        try {
            if (lock.tryLock()) {
                // 更新用户信息
                LambdaUpdateWrapper<TUser> updateWrapper = Wrappers.lambdaUpdate(TUser.class)
                        .eq(TUser::getUsername, requestParam.getUsername());
                int update = baseMapper.update(updateUser, updateWrapper);
                if (update < 1) {
                    throw new ClientException(BaseErrorCode.USER_UPDATE_ERROR);
                }
            }
        } finally {
            lock.unlock();
        }

        // 注销
        stringRedisTemplate.delete(RedisKeyConstant.USER_LOGIN_KEY + username);

    }
}
